import {
  BadRequestException,
  HttpException,
  HttpStatus,
  Injectable,
  NotFoundException,
  Inject,
  forwardRef,
} from '@nestjs/common';
import mongoose, { Model } from 'mongoose';
import { InjectModel } from '@nestjs/mongoose';
import { InjectRepository } from '@nestjs/typeorm';
import { EntityManager, ObjectID } from 'typeorm';
import * as parser from '@babel/parser';
import * as babel from '@babel/core';
import generate from '@babel/generator';
import traverse, { NodePath } from '@babel/traverse';
import * as t from '@babel/types';
import * as changeCase from 'change-case';
import { pick, isEqual } from 'lodash';

import { TypeService } from '../../superType/server/type.service';
import {
  ApiEditorDetailResDto,
  ApiEditorDocumentsResDto,
  ApiEditorExportReqDto,
  ApiEditorGetDocumentReqDto,
  ApiEditorGetDocumentResDto,
  ApiEditorGetDocumentsResDto,
  ApiEditorGetReqDto,
  ApiEditorGetResDto,
  ApiEditorTransformCodeReqDto,
  CompareMode,
  ResourceOrigin,
  ResourceStatus,
  ResourceChange,
  ApiEditorCompareProjectFoldersResDto,
} from '../types/editor.dto';
import { FileSnapshot, FileChangeWithSide } from '../types/commit.type';
import { PageEnum } from '@/types/enums/page.enum';
import {
  DocFrameType,
  LcDocument,
  LcDocumentBackup,
  LcProject,
} from '../types/editor.type';
import { getTypeMap } from '../../superType/utils/getTypeTree';
import { ITypeEntity } from '../../superType/types';
import * as fs from 'fs';
import { getRuntimePath } from '@/utils/file';
import * as path from 'path';
import { parseJson } from '../../superType/utils/transformValue';
import { UserService } from '../../../../modules/user/user.service';
import { UserEntity } from '../../user/entities/user.entity';
import { ClientRole } from '@/modules/user/role/role.type';
import { LcProjectMember } from '../types/auth.type';
import { ApiEditorUpdateMemberReqDto } from '../types/auth.dto';
import {
  ApiEditorResourceReqDto,
  ApiEditorResourceResDto,
  BelongType,
  ResourceType,
} from '../types/resource.dto';
import { transformExport } from './utils/export';
import {
  calculateDiff,
  determineHistoryType,
} from './history/determineHistoryType';
import { HistoryType } from './history/HistoryType';
import { UserRoleEntity } from '../../user/entities/user.role.entity';
import { getProjectAssetsJson } from '../utils/loadProjectAssets';
import * as moment from 'moment';
// 🆕 版本控制相关导入
import { AccessControlService } from './access-control.service';
import { VersionControlService } from './version-control.service';
import { Action } from '../types/auth.type';
import { cdnDomain } from '@/config/domain';
import { calculateDocumentHashes, calculateProjectHashes } from './virtual-file-system/utils/calculateHashs';
import { syncValidate, unallow } from '@/utils/validate';
import { getDocumentPath, getTypePath } from './virtual-file-system/utils/source-path';

@Injectable()
export class EditorService {
  constructor(
    @InjectModel('project') private projectModel: Model<LcProject>,
    @InjectModel('doc') private documentModel: Model<LcDocument>,
    @InjectModel('type') private typeModel: Model<ITypeEntity>,
    @InjectModel('doc_backup')
    private documentBackupModel: Model<LcDocumentBackup>,
    private userService: UserService,
    private accessControlService: AccessControlService,
    @Inject(forwardRef(() => VersionControlService))
    private versionControlService: VersionControlService,
  ) {}
  /**
   * 事务管理器
   */
  manager: EntityManager;

  /**
   * 清理文档对象，移除不需要记录到历史的字段
   * @param doc 文档对象
   * @param excludeFields 需要排除的字段列表
   * @returns 清理后的文档对象
   */
  private cleanDocumentForHistory(doc: any, excludeFields: string[]): any {
    const cleaned = { ...doc };

    // 移除排除的字段
    excludeFields.forEach((field) => {
      delete cleaned[field];
    });

    // 确保 schema 是对象格式（不是字符串）
    if (cleaned.schema && typeof cleaned.schema === 'string') {
      try {
        cleaned.schema = JSON.parse(cleaned.schema);
      } catch (e) {
        console.error('解析 schema 失败:', e);
      }
    }

    return cleaned;
  }

  voTypes: t.ExportNamedDeclaration[] = [];

  /**
   * 检查用户权限（支持新旧两种模式）
   * 🆕 支持匿名用户访问
   * @param projectId 项目ID或项目对象
   * @param action 操作类型
   * @param userId 用户ID（可为 null/undefined 表示匿名用户）
   * @param filePath 可选：文件路径（使用新权限系统时需要）
   */
  async checkAuth(
    projectId: string | LcProject,
    action: 'edit' | 'view' | 'export' | 'updateMember',
    userId: number | null | undefined,
    filePath?: string,
  ) {
    const project =
      typeof projectId === 'object' && projectId.member
        ? projectId
        : await this.projectModel.findById(projectId, {
            teamId: 1,
            member: 1,
            userId: 1,
          });

    // 🆕 处理匿名用户
    const anonymousUserId = userId || 0;

    // 1. 检查是否是项目所有者（匿名用户不可能是所有者）
    if (project.userId === userId) {
      return true;
    }

    // 2. 对于匿名用户，跳过团队成员检查
    if (userId) {
      const userRoles = await this.userService.getUserRole(userId);
      const teamIds = userRoles.map((item) => Number(item.team_id));
      if (teamIds.includes(project.teamId)) {
        return true;
      }
    }

    // 3. 检查项目成员权限（包括特殊的游客成员 userId: 0）
    const member = project.member?.find((_) => _.userId === anonymousUserId);
    if (!member) {
      return false;
    }

    // 🆕 4. 如果成员有新角色（包括游客），使用新的权限系统
    if (member.role || member.policies) {
      const actionMap: Record<string, Action> = {
        view: 'view',
        edit: 'source:edit',
        export: 'source:export',
        updateMember: 'grant',
      };

      const result = await this.accessControlService.checkPermission(
        anonymousUserId,
        {
          projectId: project._id.toString(),
          path: filePath || '/', // 如果没有提供文件路径，使用根路径
        },
        actionMap[action],
      );

      return result.allowed;
    }

    // 5. 否则使用旧的权限系统（向后兼容）
    const documentPermission = ['edit', 'export', 'updateMember'].includes(
      action,
    )
      ? 'edit'
      : 'view';

    return (
      member.documentPermission === documentPermission ||
      member.documentPermission === 'edit'
    );
  }

  async updateProject(
    projectId: string,
    partialProject: Partial<LcProject>,
    user: UserEntity,
    options?: {
      skipHashCalculation?: boolean;
      existProject?: LcProject;
    },
  ): Promise<LcProject> {
    const data = partialProject;
    if (!projectId) throw new BadRequestException('缺少项目id');

    const { skipHashCalculation = false, existProject: providedExistProject } = options || {};

    // 获取现有项目（用于更新时比较CSS哈希）
    let existProject = providedExistProject;
    if (!existProject) {
      existProject = await this.projectModel.findById(projectId, {}).lean();
    }

    // 保存哈希计算结果用于历史记录
    let projectHash: string | undefined;
    let cssHash: string | null = null;
    let projectContent: string | null = null;
    let cssContent: string | null = null;

    // 如果没有跳过哈希计算且有schema，重新计算HASH
    if (!skipHashCalculation) {
      try {
        const res = await calculateProjectHashes({
          ...existProject,
          ...data,
        });
        if (res.hash) {
          data.hash = res.hash;
          data.path = res.path;
          data.cssHash = res.cssHash;
          data.cssPath = res.cssPath;
          // 比较CSS哈希是否变化
          if (existProject?.cssHash !== res.cssHash) {
            data.cssUpdateAt = Date.now();
          }

          // 🆕 保存哈希和内容用于历史记录
          projectHash = res.hash;
          cssHash = res.cssHash;
          projectContent = res.projectContent; // 🆕 保存项目配置内容
          cssContent = res.cssContent; // 🆕 保存 CSS 内容
        }
      } catch(e) {
        console.error('计算项目哈希失败:', e);
      }
    }

    const session = await this.projectModel.db.startSession();

    try {
      // 在事务外声明一个变量来存储更新后的文档
      let updatedDocument: LcProject | null = null;

      // 执行事务
      await session.withTransaction(async () => {
        const doc = await this.projectModel.findOne(
          { _id: projectId },
          { schema: 1 },
          { session },
        );

        if (!doc) {
          throw new NotFoundException('项目不存在');
        }

        if (data.schemaPartial) {
          const schema =
            typeof doc.schema === 'string'
              ? JSON.parse(doc.schema)
              : doc.schema || {};

          data.schema = {
            ...schema,
            ...data.schemaPartial,
          };
          delete data.schemaPartial;
        }

        if (data.modules?.length > 0) {
          data.modules = data.modules.map((item) => {
            const { project, ...rest } = item;
            return rest;
          });
        }

        data.update_time = Date.now();
        data.lastUpdateBy = user.id;

        // 保存更新后的文档到外部变量
        updatedDocument = await this.projectModel.findOneAndUpdate(
          { _id: projectId },
          { $set: data },
          {
            new: true,
            session,
            runValidators: true,
            lean: true, // 返回普通 JavaScript 对象
          },
        );

        if (!updatedDocument) {
          throw new NotFoundException('更新失败，项目不存在');
        }
      });

      // 事务成功后返回更新的文档
      if (!updatedDocument) {
        throw new Error('更新失败，未能获取更新后的文档');
      }

      // 🆕 记录文件变更历史（项目配置和 CSS 文件）
      if (!skipHashCalculation && projectHash) {
        const projectHashChanged = existProject?.hash !== projectHash;
        const cssHashChanged = existProject?.cssHash !== cssHash;

        // 记录项目配置文件变更（/src/project.json）
        if (projectHashChanged && projectContent) {
          try {
            const isFirstVersion = !existProject || !existProject.hash;

            // ✅ 简化逻辑：直接调用 recordFileChange，内部会自动处理旧内容获取和异常
            await this.versionControlService.recordFileChange({
              projectId,
              path: '/src/project.json',
              action: isFirstVersion ? 'add' : 'modify',
              content: projectContent,
              contentHash: projectHash,
              userId: user.id,
            });

            console.log(`✅ [项目配置历史记录] 记录成功: /src/project.json`);
          } catch (error) {
            console.error(`❌ [项目配置历史记录] 记录失败: /src/project.json`, error);
          }
        }

        // 记录 CSS 文件变更（/src/app.css）
        if (cssHashChanged && cssHash && cssContent) {
          try {
            const isFirstCssVersion = !existProject || !existProject.cssHash;

            // ✅ 简化逻辑：直接调用 recordFileChange，内部会自动处理旧内容获取和异常
            await this.versionControlService.recordFileChange({
              projectId,
              path: '/src/app.css',
              action: isFirstCssVersion ? 'add' : 'modify',
              content: cssContent,
              contentHash: cssHash,
              userId: user.id,
            });

            console.log(`✅ [CSS 历史记录] 记录成功: /src/app.css`);
          } catch (error) {
            console.error(`❌ [CSS 历史记录] 记录失败: /src/app.css`, error);
          }
        }
      }

      return updatedDocument;
    } catch (error) {
      throw new Error(`更新失败: ${error.message}`);
    } finally {
      await session.endSession();
    }
  }

  /**
   * 创建项目
   * @param projectData 项目数据
   * @param userId 用户ID
   * @param teamId 团队ID
   * @returns 创建后的项目
   */
  async createProject(
    projectData: Partial<LcProject>,
    userId: number,
    teamId: number,
  ): Promise<LcProject> {
    const data = projectData;

    // 保存哈希计算结果用于历史记录
    let projectHash: string | undefined;
    let cssHash: string | null = null;
    let projectContent: string | null = null;
    let cssContent: string | null = null;

    // 如果有schema，重新计算HASH
    try {
      const res = await calculateProjectHashes({
        ...data,
      });
      if (res.hash) {
        data.hash = res.hash;
        data.path = res.path;
        data.cssHash = res.cssHash;
        data.cssPath = res.cssPath;
        data.cssUpdateAt = Date.now();

        // 🆕 保存哈希和内容用于历史记录
        projectHash = res.hash;
        cssHash = res.cssHash;
        projectContent = res.projectContent; // 🆕 保存项目配置内容
        cssContent = res.cssContent; // 🆕 保存 CSS 内容
      }
    } catch(e) {
      console.error('计算项目哈希失败:', e);
    }

    data.teamId = teamId;
    data.userId = userId;
    data.lastUpdateBy = userId;
    data.create_time = Date.now();
    data.update_time = Date.now();

    const created = new this.projectModel(data);
    created.validateSync({ validateBeforeSave: false });
    const savedProject = await created.save();

    // 🆕 记录新项目的历史（项目配置和 CSS 文件）
    if (projectHash && projectContent) {
      try {
        // 记录项目配置文件
        console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
        console.log('📋 [新项目配置历史记录] 开始记录');
        console.log('  文件路径: /src/project.json');
        console.log('  项目ID:', savedProject._id);
        console.log('  项目 hash:', projectHash);
        console.log('  配置内容长度:', projectContent.length);
        console.log('  操作类型: add');
        console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');

        await this.versionControlService.recordFileChange({
          projectId: savedProject._id.toString(),
          path: '/src/project.json',
          action: 'add',
          content: projectContent,
          contentHash: projectHash,
          userId,
        });

        console.log(`✅ [新项目配置历史记录] 记录成功: /src/project.json`);
      } catch (error) {
        console.error(`❌ [新项目配置历史记录] 记录失败: /src/project.json`, error);
      }
    }

    // 记录 CSS 文件（如果存在）
    if (cssHash && cssContent) {
      try {
        console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
        console.log('🎨 [新项目CSS历史记录] 开始记录');
        console.log('  文件路径: /src/app.css');
        console.log('  项目ID:', savedProject._id);
        console.log('  CSS hash:', cssHash);
        console.log('  CSS 内容长度:', cssContent.length);
        console.log('  操作类型: add');
        console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');

        await this.versionControlService.recordFileChange({
          projectId: savedProject._id.toString(),
          path: '/src/app.css',
          action: 'add',
          content: cssContent,
          contentHash: cssHash,
          userId,
        });

        console.log(`✅ [新项目CSS历史记录] 记录成功: /src/app.css`);
      } catch (error) {
        console.error(`❌ [新项目CSS历史记录] 记录失败: /src/app.css`, error);
      }
    }

    return savedProject;
  }

  /**
   * 保存文档（供 Controller 和 VFS Service 复用）
   * @param documentData 文档数据
   * @param options 保存选项
   * @returns 保存后的文档
   */
  async saveDocument(
    documentData: Partial<LcDocument> & { _id?: string },
    options: {
      userId: number;
      teamId?: number;
      existingDoc?: LcDocument;
      skipValidation?: boolean;
      skipBackup?: boolean;
      skipPermissionCheck?: boolean;
    },
  ): Promise<LcDocument> {
    const { _id, ...params } = documentData;

    try {
      await syncValidate(params, {
        fileName: {
          required: true,
          enable: () => params.type !== 'Block' && !_id,
        },
        projectId: {
          required: true,
          enable: () => !_id,
        },
        type: {
          required: true,
          enable: () => !_id,
        },
        title: {
          required: true,
          enable: () => !_id,
        },
        schema: {
          type: 'object',
          required: true,
          enable: () => !_id,
        },
      });
    } catch (e) {
      throw new HttpException(e.message, HttpStatus.BAD_REQUEST);
    }

    const data: any = unallow(params, ['create_time', 'update_time']);

    const { userId, teamId, skipValidation, skipBackup, skipPermissionCheck } =
      options;

    // 🔧 验证必需字段
    if (!data.fileName && !_id) {
      throw new BadRequestException('创建文档时必须提供 fileName');
    }
    if (!data.fileName && !options.existingDoc?._id) {
      // 如果是更新操作但没有 fileName，尝试从数据库获取
      const existingDoc = await this.documentModel.findById(_id).lean();
      if (!existingDoc?.fileName) {
        throw new BadRequestException('文档缺少 fileName 字段');
      }
    }

    // 获取现有文档（只查询一次）
    let existDoc: LcDocument | null = options.existingDoc || null;
    if (_id && !options.existingDoc) {
      existDoc = await this.documentModel.findById(_id).lean();
    }
    // 设置默认值
    if (!data.type) {
      data.type = existDoc?.type || 'Page';
    }
    if (!data.folder) {
      data.folder = existDoc?.folder || '/src/pages';
    }
    if (!data.fileName && existDoc?.fileName) {
      data.fileName = existDoc.fileName;
    }

    // 重新计算 HASH（用于检测代码变化）
    let tsxHash: string | undefined;
    let cssHash: string | null = null;
    let tsxContent: string | null = null; // 🆕 TSX 代码内容
    let cssContent: string | null = null; // 🆕 CSS 代码内容
    let docPath: string | undefined; // .doc 文件路径
    let cssPath: string | null = null; // 🆕 CSS 文件路径

    try {
      // 合并文档数据，确保 schema 字段有效
      const mergedDoc = {
        ...existDoc,
        ...data,
      };

      // 🔧 如果 schema 是 undefined，使用现有文档的 schema 或空对象
      if (!mergedDoc.schema && existDoc?.schema) {
        mergedDoc.schema = existDoc.schema;
      } else if (!mergedDoc.schema) {
        // 如果都没有 schema，跳过 hash 计算
        console.warn(
          `[EditorService] 文档缺少 schema，跳过 hash 计算: ${data.fileName}`,
        );
        throw new Error('文档缺少 schema');
      }

      // console.log('mergedDoc: ', mergedDoc);

      const res = await calculateDocumentHashes(mergedDoc);
      // console.log('res: ', res);
      // if (res.tsxHash !== existDoc?.hash) {
      //   throw new BadRequestException('fuck');
      // }
      if (res.tsxHash) {
        data.hash = res.tsxHash;
        data.path = res.path;
        data.cssHash = res.cssHash;
        data.cssPath = res.cssPath;
        if (existDoc?.cssHash !== res.cssHash) {
          data.cssUpdateAt = Date.now();
        }

        // 🆕 保存 hash 和内容用于历史记录
        tsxHash = res.tsxHash;
        cssHash = res.cssHash;
        tsxContent = res.tsxContent; // 🆕 保存 TSX 代码
        cssContent = res.cssContent; // 🆕 保存 CSS 代码
        cssPath = res.cssPath; // 🆕 保存 CSS 路径
        // 获取 .doc 文件路径（而不是 .tsx）
        docPath = getDocumentPath({ ...existDoc, ...data });
      }
    } catch (e) {
      console.error('计算文档哈希失败:', e);
    }

    console.log('data.schema.css: ', data.schema);

    // 序列化 schema
    if (data.schema) {
      data.schema =
        typeof data.schema === 'string'
          ? data.schema
          : JSON.stringify(data.schema);
    }

    data.lastUpdateBy = userId;
    data.update_time = Date.now();

    // 更新现有文档
    if (_id) {
      // 权限检查
      if (!skipPermissionCheck) {
        const havePermission = await this.checkAuth(
          data.projectId || existDoc?.projectId,
          'edit',
          userId,
        );
        if (!havePermission) {
          throw new BadRequestException('您无权操作');
        }
      }
      console.log('updateOne');
      // 保存
      await this.documentModel.updateOne({ _id }, data);

      // 🆕 记录文件变更历史（TSX 和 CSS 文件）
      const hashChanged = existDoc?.hash !== tsxHash;
      console.log('hashChanged: ', hashChanged);
      const cssHashChanged = existDoc?.cssHash !== cssHash;
      console.log('cssHashChanged: ', cssHashChanged);

      // 优化：如果只是更新截图等元数据（没有 schema），跳过历史记录
      const isMetadataOnlyUpdate = !data.schema && !params.schema;

      // 🔧 记录 TSX 文件变更（hash 变化时）
      if (hashChanged && !isMetadataOnlyUpdate && tsxHash && tsxContent) {
        try {
          const tsxPath = docPath.replace(/\.doc$/, '.tsx');
          const isFirstVersion = !existDoc || !existDoc.hash;

          // ✅ 简化逻辑：直接调用 recordFileChange，内部会自动处理旧内容获取和异常
          await this.versionControlService.recordFileChange({
            projectId: data.projectId || existDoc?.projectId,
            path: tsxPath,
            action: isFirstVersion ? 'add' : 'modify',
            content: tsxContent,
            contentHash: tsxHash,
            userId,
          });

          console.log(`✅ [TSX 历史记录] 记录成功: ${tsxPath}`);
        } catch (error) {
          console.error(`❌ [TSX 历史记录] 记录失败: ${docPath}`, error);
          // ⚠️ 历史记录失败不应该阻塞文档保存
        }
      }

      // 🔧 记录 CSS 文件变更（cssHash 变化时）
      if (cssHashChanged && !isMetadataOnlyUpdate && cssHash && cssContent) {
        try {
          const isFirstCssVersion = !existDoc || !existDoc.cssHash;

          // ✅ 简化逻辑：直接调用 recordFileChange，内部会自动处理旧内容获取和异常
          await this.versionControlService.recordFileChange({
            projectId: data.projectId || existDoc?.projectId,
            path: cssPath,
            action: isFirstCssVersion ? 'add' : 'modify',
            content: cssContent,
            contentHash: cssHash,
            userId,
          });

          console.log(`✅ [CSS 历史记录] 记录成功: ${cssPath}`);
        } catch (error) {
          console.error(`❌ [CSS 历史记录] 记录失败: ${cssPath}`, error);
        }
      }

      return {
        _id,
        ...data,
      } as LcDocument;
    } else {
      // 创建新文档
      data.teamId = teamId;
      data.userId = userId;
      data.create_time = Date.now();
      const doc = new this.documentModel(data);
      if (!skipValidation) {
        doc.validateSync({ validateBeforeSave: false });
      }
      const savedDoc = await doc.save();

      // 🆕 记录新文件的历史（TSX 和 CSS 文件）
      if (tsxHash && tsxContent) {
        try {
          const tsxPath = docPath.replace(/\.doc$/, '.tsx');

          // 记录 TSX 文件
          await this.versionControlService.recordFileChange({
            projectId: data.projectId,
            path: tsxPath,
            action: 'add',
            content: tsxContent,
            contentHash: tsxHash,
            userId,
          });
          console.log(`✅ [新文档 TSX 历史记录] 记录成功: ${tsxPath}`);
        } catch (error) {
          console.error(`❌ [新文档 TSX 历史记录] 记录失败: ${docPath}`, error);
        }
      }

      // 🆕 记录新文件的 CSS 历史
      if (cssHash && cssContent && cssPath) {
        try {
          await this.versionControlService.recordFileChange({
            projectId: data.projectId,
            path: cssPath,
            action: 'add',
            content: cssContent,
            contentHash: cssHash,
            userId,
          });
          console.log(`✅ [新文档 CSS 历史记录] 记录成功: ${cssPath}`);
        } catch (error) {
          console.error(`❌ [新文档 CSS 历史记录] 记录失败: ${cssPath}`, error);
        }
      }

      return savedDoc;
    }
  }

  /**
   * 写一个在用于每次保存时触发的备份方法，写一个根据时间间隔备份，每个文档只根据保存时间进行替换，仅保留几个不同版本的备份
   */
  async backup(doc: LcDocument, userId: number) {
    //
  }

  async cleanupOldBackups(docId: string) {
    const limits = {
      [HistoryType.SHORT_TERM]: 12,
      [HistoryType.MID_TERM]: 24,
      [HistoryType.LONG_TERM]: 30,
    };

    for (const [type, limit] of Object.entries(limits)) {
      const backups = await this.documentBackupModel
        .find({ docId, type })
        .sort({ createAt: -1 })
        .skip(limit);

      if (backups.length > 0) {
        await this.documentBackupModel.deleteMany({
          _id: { $in: backups.map((b) => b._id) },
        });
      }
    }
  }

  /**
   * 触发备份策略
   */
  async checkBackup(doc: LcDocument, userId: number) {
    try {
      const sourceDocument = await this.documentModel.findById(doc._id);
      if (!sourceDocument) {
        throw new Error('Document not found');
      }

      const lastBackup = await this.documentBackupModel
        .findOne({
          docId: sourceDocument._id,
        })
        .sort({ createAt: -1 });

      const type = determineHistoryType(
        sourceDocument.update_time || Date.now(),
      );
      let willSaveBackup = false;

      if (sourceDocument.lastUpdateBy !== userId) {
        willSaveBackup = true;
      } else if (!lastBackup) {
        willSaveBackup = true;
      } else {
        const diff = calculateDiff(lastBackup.data, sourceDocument);
        if (Object.keys(diff).length > 0) {
          const timeSinceLastBackup = moment().diff(
            moment(lastBackup.createAt),
            'minutes',
          );
          switch (type) {
            case HistoryType.SHORT_TERM:
              willSaveBackup = timeSinceLastBackup >= 15;
              break;
            case HistoryType.MID_TERM:
              willSaveBackup = timeSinceLastBackup >= 60;
              break;
            case HistoryType.LONG_TERM:
              willSaveBackup = timeSinceLastBackup >= 360;
              break;
          }
        }
      }

      if (willSaveBackup) {
        const data = {
          data: sourceDocument,
          docId: sourceDocument._id,
          type,
          projectId: sourceDocument.projectId,
          teamId: sourceDocument.teamId || 0,
          userId: sourceDocument.userId || 0,
          createAt: sourceDocument.update_time || Date.now(),
        };

        const newBackup = await this.documentBackupModel.create(data);
        await this.cleanupOldBackups(sourceDocument._id);
        return newBackup;
      }

      return null;
    } catch (error) {
      console.error('Backup failed:', error);
      throw error;
    }
  }

  /**
   * 计算storeInfo.stat统计信息
   */
  async calcStat(projectId: string | LcProject) {
    const project =
      typeof projectId === 'object' && projectId
        ? projectId
        : await this.projectModel.findById(projectId);
    const { actionCount = 0, installCount = 0 } = project.storeInfo?.stat || {};
    let {
      pageCount = 0,
      componentCount = 0,
      blockCount = 0,
      assetCount = 0,
      pluginCount = 0,
    } = project.storeInfo?.stat || {};
    pageCount = await this.documentModel.count({
      projectId,
      share: true,
      type: 'Page',
    });
    componentCount = await this.documentModel.count({
      projectId,
      share: true,
      type: 'Component',
    });
    blockCount = await this.documentModel.count({ projectId, type: 'Block' });
    assetCount = project.assets.filter((_) => _.autoInstall).length;
    pluginCount = project.storeInfo?.plugins.length || 0;
    return {
      pageCount,
      componentCount,
      blockCount,
      actionCount,
      pluginCount,
      assetCount,
      installCount,
    };
  }

  getResource(
    params: ApiEditorResourceReqDto,
    user?: UserEntity,
    role?: ClientRole,
  ) {
    return new Promise<ApiEditorResourceResDto>(async (resolve, reject) => {
      const {
        currentPage,
        pageSize,
        trade,
        childTrade,
        scene,
        childScene,
        type,
        projectId,
        keywords,
        belong,
        teamId,
      } = params;
      let filterTeamId = teamId;
      if (belong === 'team') {
        filterTeamId = role.teamId;
      }
      const res = await this.getDocument(
        {
          currentPage,
          pageSize,
          type: (['Block', 'Component', 'Page'].includes(type)
            ? type
            : undefined) as ApiEditorGetDocumentReqDto['type'],
          trade: trade || undefined,
          childTrade: childTrade || undefined,
          scene: scene || undefined,
          childScene: childScene || undefined,
          projectId,
          keywords,
          source: false,
          belong,
          teamId: filterTeamId,
        },
        {
          select: {
            schema: 0,
            types: 0,
          },
          resource: true,
          resourceParams: params,
        },
      );
      resolve(res);
    });
  }

  /**
   * 获取文档
   * @deprecated 废弃（跟getDocuments重复）
   */
  getDocumentAndImport(
    params: ApiEditorGetReqDto,
    user?: UserEntity,
    role?: ClientRole,
  ) {
    return new Promise<ApiEditorDocumentsResDto>(async (resolve, reject) => {
      const {
        _id: projectId,
        afterTime,
        fileName,
        fileNames,
        folders,
        frame,
        documentId,
        type,
      } = params;
      const { data: documents } = await this.getDocument({
        projectId,
        afterTime,
        fileName,
        fileNames,
        frame,
        folders,
        _id: documentId,
        type,
      });

      // 根据文档引用低代码组件，拉取对应的组件
      const componentDocs: LcDocument[] = [];
      const finish = () => {
        resolve({
          documents,
          componentDocs,
        });
      };
      const getImportDocument = async (docs) => {
        const componentProjectIds: Record<string, string[]> = {};
        docs.forEach((doc) => {
          const componentsMap =
            doc.componentsRoot?.[frame] || doc.componentsMap;
          componentsMap?.forEach((item) => {
            const componentFileNames =
              componentProjectIds[item.package || doc.projectId];
            if (
              item.devMode === 'lowCode' &&
              !componentFileNames?.includes(item.componentName)
            ) {
              if (
                !componentDocs
                  .map((_) => _.fileName)
                  .includes(item.componentName)
              )
                if (componentFileNames) {
                  componentFileNames.push(item.componentName);
                } else {
                  componentProjectIds[item.package || doc.projectId] = [
                    item.componentName,
                  ];
                }
            }
          });
        });
        const projectIds = Object.keys(componentProjectIds);
        if (projectIds.length > 0) {
          for (let i = 0; i < projectIds.length; i++) {
            const projectId = projectIds[i];
            const componentFileNames = componentProjectIds[projectId];
            if (componentFileNames.length > 0) {
              const { data } = await this.getDocument({
                projectId,
                type: 'Component',
                fileNames: Array.from(new Set(componentFileNames)),
              });
              // @TODO: 这里理论上可以加载所有的依赖文档包括模块中的文件，需要验证一下
              data.forEach((item) => {
                const existIndex = componentDocs.findIndex(
                  (_) => _.fileName === item.fileName,
                );
                if (existIndex < 0) {
                  componentDocs.push(item);
                }
              });
              if (data.length > 0) {
                await getImportDocument(data);
              } else {
                finish();
              }
            } else {
              finish();
            }
          }
        } else {
          finish();
        }
      };
      // 根据文档引用低代码组件，拉取对应的组件(此处根据fileNames参数来决定是否拉取，应为默认会加载所有)
      if (documents.length > 0 && (fileName || fileNames)) {
        getImportDocument(documents);
      } else {
        finish();
      }
    });
  }

  getProjectAndDocuments(
    params: ApiEditorGetReqDto,
    isExport?: boolean,
    user?: UserEntity,
    role?: ClientRole,
  ) {
    console.log('getProjectAndDocuments: ', params);
    const {
      _id: projectId,
      afterTime,
      fileName,
      fileNames,
      folders,
      documentId,
      frame,
      mode,
    } = params;
    if (!projectId && !documentId) {
      throw new BadRequestException('项目ID不能为空');
    }
    return new Promise<ApiEditorDetailResDto>(async (resolve, reject) => {
      const { data: projects } = await this.getProject(params, user?.id);
      if (!projects?.[0]) {
        throw new BadRequestException('项目不存在');
      }
      let project = JSON.parse(JSON.stringify(projects[0])) as LcProject;
      const { modules, assets } = await this.mergeModuleAssets(
        project,
        user,
        role,
      );
      project = {
        ...project,
        assets,
        modules,
      };
      const currentFrameAssets = getProjectAssetsJson(
        project,
        frame as DocFrameType,
      );
      // const currentFramePackages = [];
      // let currentPackages = await filterPackages(__assets.packages);
      // 补充projectAsset中的packages
      // const projectAssets = loadProjectAssets(
      //   { packages: [], components: [], version: '' },
      //   toJS(this.currentProject.assets),
      //   this.currentFrame,
      // );

      // currentPackages = currentPackages.concat(projectAssets.packages);
      // console.log('projectAssets: ', projectAssets);
      // 转换旧模型
      // const packages: Record<DocFrameType, IPublicTypePackage[]> = Array.isArray(toJS(prj.packages))
      //   ? ({
      //       [prj.type as DocFrameType]: prj.packages as IPublicTypePackage[],
      //     } as Record<DocFrameType, IPublicTypePackage[]>)
      //   : (prj.packages as Record<DocFrameType, IPublicTypePackage[]>);
      // // 存储当前数据
      if (Array.isArray(project.packages)) {
        project.packages = currentFrameAssets.packages;
      } else {
        project.packages = {
          ...(project.packages || {}),
          [frame as DocFrameType]: currentFrameAssets.packages || [],
        } as any;
      }
      const res = await this.getDocuments(
        {
          frame: params.frame || project.type,
          ...params,
        },
        isExport,
        user,
        role,
      );
      if (isExport) {
        return resolve(
          transformExport(
            {
              project,
              ...res,
            },
            {
              frame,
            },
          ),
        );
      } else {
        return resolve({
          project,
          ...res,
        });
      }
    });
  }

  getDocuments(
    params: ApiEditorGetReqDto,
    isExport?: boolean,
    user?: UserEntity,
    role?: ClientRole,
  ) {
    const projectId = params.projectId || params._id;
    const {
      _id,
      _ids,
      afterTime,
      fileName,
      fileNames,
      folders,
      frame,
      documentId,
      mode,
      type,
      source,
      getImport = true,
    } = params;
    if (!projectId && !documentId && !_ids) {
      throw new BadRequestException('项目ID不能为空');
    }
    return new Promise<ApiEditorGetDocumentsResDto>(async (resolve, reject) => {
      // const currentFrame = frame || project.type;
      const fixBugPages = [];

      let documents = [];
      if (_ids || fileName || documentId || mode === 'design' || isExport) {
        const res = await this.getDocument({
          projectId,
          afterTime,
          fileName,
          fileNames,
          frame,
          folders,
          _id: documentId,
          _ids,
          type,
          source,
        });
        documents = res.data;
      }

      if (
        isExport &&
        documents.length === 1 &&
        documents[0].type === 'Component'
      ) {
        // 随便查一个相同框架的
        const { data: _documents } = await this.getDocument({
          projectId,
          frame,
          type: 'Page',
          currentPage: 1,
          pageSize: 1,
          source,
        });
        if (_documents?.[0]) {
          // fixBugPages.push(_documents?.[0]);
          documents.push(_documents?.[0]);
        }
      }

      // 根据文档引用低代码组件，拉取对应的组件
      const componentDocs: LcDocument[] = [];
      const finish = async () => {
        // project.canEdit = await this.checkAuth(project, 'edit', user.id);
        const res = {
          documents: documents.concat(fixBugPages),
          componentDocs,
        };
        resolve(res);
      };
      const getImportDocument = async (docs) => {
        const componentProjectIds: Record<string, string[]> = {};
        docs.forEach((doc) => {
          const componentsMap =
            doc.componentsRoot?.[frame] || doc.componentsMap;
          componentsMap?.forEach((item) => {
            const componentFileNames =
              componentProjectIds[item.projectId || projectId];
            if (
              item.devMode === 'lowCode' &&
              !componentFileNames?.includes(item.componentName)
            ) {
              if (
                !componentDocs
                  .concat(documents)
                  .map((_) => _.fileName)
                  .includes(item.componentName)
              ) {
                if (componentFileNames) {
                  componentFileNames.push(item.componentName);
                } else {
                  componentProjectIds[item.projectId || projectId] = [
                    item.componentName,
                  ];
                }
              }
            }
          });
        });
        const projectIds = Object.keys(componentProjectIds);
        if (projectIds.length > 0) {
          for (let i = 0; i < projectIds.length; i++) {
            const projectId = projectIds[i];
            const componentFileNames = componentProjectIds[projectId];
            if (componentFileNames.length > 0) {
              console.log('getDocument: ', {
                projectId: projectId === 'undefined' ? undefined : projectId,
                type: 'Component',
                source,
                fileNames: Array.from(new Set(componentFileNames)),
              });
              const { data } = await this.getDocument({
                projectId: projectId === 'undefined' ? undefined : projectId,
                type: 'Component',
                source,
                fileNames: Array.from(new Set(componentFileNames)),
              });

              data.forEach((item) => {
                const existIndex = componentDocs.findIndex(
                  (_) => _.fileName === item.fileName,
                );
                if (existIndex < 0) {
                  componentDocs.push(item);
                }
              });
              if (data.length > 0) {
                await getImportDocument(data);
              }
              // console.log('data: ', data);
            }
          }
          finish();
        } else {
          finish();
        }
      };
      if (documents.length > 0 && getImport) {
        getImportDocument(documents);
      } else {
        finish();
      }
    });
  }

  async getProject(
    params: ApiEditorGetReqDto,
    user_id?: number,
    teamId?: number,
  ): Promise<ApiEditorGetResDto> {
    const {
      currentPage = PageEnum.PAGE_NUMBER,
      pageSize = PageEnum.PAGE_NUMBER,
      count,
      _id,
      _ids,
      mode,
      fileName,
      fileNames,
      keywords,
      isTemplate,
      templateTab,
      templateType,
      belong,
      isFollowed,
      isDeleted,
      isModule,
      haveCanvas,
      queryList,
    } = params;
    const query = this.projectModel;
    // 获取TeamIds
    let teamIds = [];
    if (user_id) {
      const userRoles = await this.userService.getUserRole(user_id);
      teamIds = userRoles.map((item) => item.team_id);
    }
    let where: Partial<LcProject & { $or; $and }> = {
      deleteAt: null,
    };
    console.log('getProject: ', params);
    if (_id) {
      Object.assign(where, {
        _id,
      });
    }
    if (_ids) {
      Object.assign(where, {
        _id: { $in: _ids },
      });
    }
    if (fileName) {
    }
    if (keywords) {
      Object.assign(where, {
        title: {
          $regex: keywords,
        },
      });
    }
    if (isTemplate) {
      Object.assign(where, {
        isTemplate: 1,
      });
      if (templateType) {
        Object.assign(where, {
          type: templateType,
        });
      }
      if (templateTab) {
        // Object.assign(where, {
        //   templateTags: templateTab,
        // });
      }
    }
    if (haveCanvas) {
      Object.assign(where, {
        canvas: {
          $ne: null,
        },
      });
    }
    if (belong) {
      where['storeInfo.isRelease'] = true;
      where['storeInfo.belong'] = belong;
    }
    if (isModule) {
      where['storeInfo.isModule'] = true;
    }
    if (params.teamId) {
      Object.assign(where, {
        teamId: params.teamId,
      });
    }
    if (params.isFollowed) {
      Object.assign(where, {
        followUserIds: user_id,
      });
    }
    if (typeof params.isDeleted === 'boolean') {
      Object.assign(where, {
        deleteAt: params.isDeleted ? { $ne: null } : null,
      });
    }
    // 基础权限判断
    // console.log('teamIds: ', teamIds);
    let baseOrWhere: any[] = [
      // {
      //   member: {
      //     $elemMatch: {
      //       userId: user_id,
      //       documentPermission: 'edit',
      //     }
      //   }
      // }
    ];
    if (user_id) {
      baseOrWhere.push(
        ...[{ teamId: { $in: teamIds } }, { 'member.userId': user_id }],
      );
    }
    const baseAuthWhere: Record<string, any> = {};
    if (baseOrWhere.length > 0) {
      baseAuthWhere.$or = baseOrWhere;
    }
    // 🆕 公开访问支持：对特定项目查询的特殊处理
    if (_id || _ids) {
      const publicAccessConditions = {
        $or: [
          { publicIdentifier: { $exists: true, $ne: null } },
          { 'member.userId': 0 }, // 游客成员
          { isPublic: true }, // 保持向后兼容
          { 'storeInfo.isRelease': true }, // 发布的项目
        ],
      };

      if (Object.keys(baseAuthWhere).length > 0) {
        // 有用户权限条件时的查询：(ID且用户有权限) OR (ID且公开访问)
        where = {
          $or: [
            {
              $and: [
                where, // 包含 _id 条件
                baseAuthWhere, // 用户权限条件
              ],
            },
            {
              $and: [
                where, // 包含 _id 条件
                publicAccessConditions, // 公开访问条件
              ],
            },
          ],
        };
      } else {
        // 匿名用户时，直接使用公开访问条件
        where = {
          $and: [
            where, // 包含 _id 条件
            publicAccessConditions, // 公开访问条件
          ],
        };
      }
    } else if (!isTemplate && !isModule) {
      // 对于列表查询，保持原有逻辑
      if (Object.keys(baseAuthWhere).length > 0) {
        where = {
          $and: [where, baseAuthWhere],
        };
      }
    }
    console.log('where: ', JSON.stringify(where));
    console.log('teamIds: ', teamIds);
    console.log('baseAuthWhere: ', baseAuthWhere);

    // console.log('queryList: ', queryList);
    const data = await query
      .where(where)
      .find()
      .skip((currentPage - 1) * pageSize)
      .limit(pageSize)
      .sort({ update_time: 'desc' })
      .select(
        queryList
          ? {
              schema: 0,
              types: 0,
              canvas: 0,
              assets: 0,
              documents: 0,
              packages: 0,
              themes: 0,
            }
          : false,
      )
      .exec();
    const total = count
      ? await this.projectModel.where(where).count().exec()
      : 0;
    const _data = [];
    if (data?.length > 0) {
      for (let i = 0; i < data.length; i++) {
        const project = data[i].toJSON() as any;
        if (data.length === 1) {
          project.canEdit = true;
          if (user_id) {
            project.canEdit = await this.checkAuth(project, 'edit', user_id);
          }
        }
        if (user_id) {
          project.isFollowed = !!project.followUserIds?.includes(user_id);
        }
        _data.push(project);
      }
    }
    return {
      currentPage,
      pageSize,
      data: _data,
      total,
    };
  }

  async getDocument(
    params: ApiEditorGetDocumentReqDto,
    queryOptions: {
      select?: string[] | string | Record<string, number>;
      resource?: boolean;
      resourceParams?: ApiEditorResourceReqDto;
    } = {},
  ): Promise<ApiEditorGetDocumentResDto> {
    const {
      currentPage = PageEnum.PAGE_NUMBER,
      pageSize = 300,
      count,
      projectId,
      _id,
      _ids,
      afterTime,
      type,
      fileName,
      fileNames,
      folders,
      branch,
      share,
      trade,
      childTrade,
      scene,
      childScene,
      keywords,
      source,
      belong,
      teamId,
    } = params;
    let { frame } = params;
    const { resource, resourceParams } = queryOptions || {};
    let { select } = queryOptions || {};
    if (source === false) {
      select = {
        schema: 0,
        types: 0,
      };
    }
    const query = this.documentModel;
    const where: Record<string, any> = JSON.parse(
      JSON.stringify({
        trade,
        childTrade,
        scene,
        childScene,
        share,
      }),
    );
    if (!share && !(_id || _ids || fileNames || fileName)) {
      where.share = { $not: { $eq: true } };
    }
    const projectWhere: Record<string, any> = {};
    if (resourceParams) {
      const { type, belong = BelongType.Store } = resourceParams;
      if (type) {
        where.type = type !== 'All' ? type : 'Block';
      }
      // 查看当前项目资源无需要判断是否发布
      if (belong !== BelongType.Project) {
        projectWhere['project.storeInfo.belong'] = belong;
        projectWhere['project.storeInfo.isRelease'] = true;
      }
      if (where.type !== 'Block') {
        where.share = true;
      }
      if (resourceParams.frame) {
        frame = resourceParams.frame;
      }
    } else {
      if (!type && !_id && !_ids && !fileName && !fileNames) {
        where.type = { $ne: 'Block' };
      }
    }
    if (projectId) {
      if (!resource) {
        Object.assign(where, {
          projectId,
        });
      } else {
        projectWhere['project._id'] = new mongoose.Types.ObjectId(projectId);
      }
    }
    if (teamId) {
      projectWhere['project.teamId'] = teamId;
    }
    if (_id) {
      Object.assign(where, {
        _id,
      });
    }
    if (_ids) {
      Object.assign(where, {
        _id: { $in: _ids },
      });
    }
    if (fileName) {
      Object.assign(where, {
        fileName,
      });
    }
    if (fileNames) {
      Object.assign(where, {
        fileName: { $in: fileNames },
      });
    }
    if (frame) {
      Object.assign(where, {
        frames: frame,
      });
    }
    if (folders) {
      Object.assign(where, {
        folder: { $in: folders },
      });
    }
    if (afterTime) {
      where.update_time = {
        $gt: afterTime,
      };
    }
    if (keywords) {
      Object.assign(where, {
        $or: [
          {
            title: { $regex: keywords, $options: 'i' },
          },
          {
            description: { $regex: keywords, $options: 'i' },
          },
        ],
      });
    }
    // console.log('resource: ', resource);
    // console.log('where: ', where);
    // console.log('projectWhere: ', projectWhere);
    const data = await (resource
      ? query
          .aggregate([
            {
              $match: where,
            },
            {
              $lookup: {
                from: 'projects', // 关联表名
                localField: 'projectId',
                foreignField: '_id',
                as: 'project',
              },
            },
            {
              $match: projectWhere,
            },
            {
              $project: {
                title: 1,
                projectId: 1,
                description: 1,
                screenshot: 1,
                fileName: 1,
                type: 1,
                frames: 1,
                width: 1,
                height: 1,
                trade: 1,
                childTrade: 1,
                scene: 1,
                childScene: 1,
                _id: 1,
                componentsMap: 1,
                componentsRoot: 1,
                from: 1,
                tags: 1,
                poster: 1,
                icon: 1,
                route: 1,
                // schema: source === false ? 0 : 1,
                // types: source === false ? 0 : 1,
                // 'project.title': 1,
              },
            },
            {
              $skip: (currentPage - 1) * pageSize,
            },
            {
              $limit: pageSize,
            },
          ])
          .exec()
      : query
          .where(where)
          .select(select)
          .skip((currentPage - 1) * pageSize)
          .limit(pageSize)
          .find()
          .exec());
    const documentsData = data.map((doc) => {
      if (doc.folder && !doc.folder.startsWith('/')) {
        doc.folder = '/src/pages/' + doc.folder;
      }
      return doc;
    });
    const total = count
      ? await this.documentModel.where(where).count().exec()
      : 0;
    return {
      currentPage,
      pageSize,
      data: documentsData,
      total,
    };
  }

  mergeModuleAssets = async (
    project: LcProject,
    u?: UserEntity,
    role?: ClientRole,
  ) => {
    const modules = project.modules?.slice() || [];
    const assets = project.assets?.slice() || [];

    if (modules?.length > 0) {
      const moduleProjectIds = modules
        ?.filter((_) => !!_.enable)
        .map((_) => _.projectId);
      const { data: moduleProjects } = await this.getProject(
        {
          _ids: moduleProjectIds,
          isModule: true,
          source: false,
          pageSize: 100,
        },
        u?.id,
        role?.teamId,
      );
      modules
        ?.filter((_) => !!_.enable)
        .forEach((module) => {
          const moduleProject = moduleProjects.find(
            (_) => _._id.toString() === module.projectId,
          );
          // 合并Assets
          const moduleAssets = moduleProject?.assets;
          if (moduleAssets?.length > 0) {
            // 以assets和moduleAssets中共同的package.package去重，assets中已存在的替换，不存在的则插入moduleAssets中的记录
            moduleAssets
              .filter((asset) => !!asset.autoInstall)
              .forEach((asset) => {
                // 为 Asset 增加模块项目ID用于标记所属模块（Editor中InstallResource中同样存在相同逻辑）
                asset.projectId = moduleProject._id;
                const index = assets.findIndex(
                  (_) => _.package?.package === asset.package?.package,
                );

                console.log('asset: ', asset);
                // CDN域名替换
                if (asset.package?.urls) {
                  asset.package.urls = asset.package?.urls.map((url) =>
                    url
                      .replace('cdn.appthen.cn', cdnDomain)
                      .replace('cdn.appthen.com', cdnDomain),
                  );
                }
                delete asset.autoInstall;
                if (index > -1) {
                  assets[index] = asset;
                } else {
                  assets.push(asset);
                }
              });
          }

          module.project = moduleProject;
        });
    }
    const resultAssetAntd = assets.find(
      (_) => _.package?.package === '@appthen/antd',
    );
    console.log(
      'resultAssetAntd: ',
      resultAssetAntd?.autoInstall,
      resultAssetAntd?.package?.urls,
    );
    return {
      modules,
      assets,
    };
  };

  // 🆕 版本控制相关方法已移至 VersionControlService

  // 🆕 模块发布相关方法已移至专门的模块服务

  compareLogEnabled({
    typeEntity,
    doc,
  }: {
    typeEntity?: ITypeEntity;
    doc?: LcDocument;
  } = {}) {
    const matched =
      typeEntity?.desc === '订单系统说明' && typeEntity.type === 'code';
    console.log('matched', matched, typeEntity?.desc, typeEntity?.type);
    return matched;
  }

  /**
   * 比较两个项目目录下的资源差异
   */
  async compareProjectFolders(params: {
    sourceProjectId: string;
    targetProjectId: string;
    sourceFolder?: string;
    targetFolder?: string;
    mode?: CompareMode;
    options?: {
      compareConfig?: boolean;
      compareAssets?: boolean;
      fileTypes?: Array<'document' | 'type'>;
      fileIds?: string[];
      includeDeleted?: boolean;
    };
  }): Promise<ApiEditorCompareProjectFoldersResDto> {
    const {
      sourceProjectId,
      targetProjectId,
      sourceFolder = '/',
      targetFolder = '/',
      mode = CompareMode.BIDIRECTIONAL,
      options = {
        compareConfig: true,
        compareAssets: true,
        fileTypes: ['document', 'type'],
        includeDeleted: true,
      },
    } = params;

    // 获取两个项目的基础资源信息
    const [sourceResources, targetResources] = await Promise.all([
      this.getAllProjectResources({
        projectId: sourceProjectId,
        folder: sourceFolder,
      }),
      this.getAllProjectResources({
        projectId: targetProjectId,
        folder: targetFolder,
      }),
    ]);

    // 初始化变更记录
    const changes: ApiEditorCompareProjectFoldersResDto['changes'] = {
      project: {},
      documents: {},
      types: {},
    };

    // 如果需要比较项目配置
    if (options.compareConfig) {
      changes.project = {
        basic: await this.compareProjectBasicInfo(
          sourceResources.project,
          targetResources.project,
        ),
        config: await this.compareProjectConfig(
          sourceResources.project,
          targetResources.project,
        ),
      };
    }

    // 如果需要比较项目资源
    if (options.compareAssets) {
      changes.project = {
        ...changes.project,
        assets: await this.compareProjectAssets(
          sourceResources.project,
          targetResources.project,
        ),
      };
    }

    // 根据比较模式处理文档和类型比较
    if (mode === CompareMode.SPECIFIED_FILES && options.fileIds?.length > 0) {
      // 仅比较指定文件
      await this.compareSpecifiedFiles(
        sourceResources,
        targetResources,
        options.fileIds,
        changes,
      );
    } else if (mode === CompareMode.UNIDIRECTIONAL) {
      // 单向拉取模式
      if (options.fileTypes?.includes('document')) {
        console.log('单向拉取模式 - 开始处理文档，交换源和目标顺序');
        const documentChanges = await this.compareResourcesMetadata(
          targetResources.documents, // 本地项目作为 source
          sourceResources.documents, // 远程项目作为 target
          (doc) => `${doc.fileName || ''}_${doc.folder || ''}`,
          async (sourceDoc, targetDoc) => {
            if (this.compareLogEnabled({ doc: sourceDoc as LcDocument })) {
              console.log('比较回调 - 文档信息:', {
                sourceDoc: sourceDoc
                  ? {
                      id: sourceDoc._id,
                      fileName: sourceDoc.fileName,
                      from: sourceDoc.from,
                      version: sourceDoc.version,
                    }
                  : null,
                targetDoc: targetDoc
                  ? {
                      id: targetDoc._id,
                      fileName: targetDoc.fileName,
                      version: targetDoc.version,
                    }
                  : null,
                targetProjectId,
              });
            }

            // 如果本地文档存在且来源是MODULE
            if (sourceDoc && sourceDoc.from?.projectId === targetProjectId) {
              if (!targetDoc) {
                // 远程已删除
                console.log('检测到远程删除:', sourceDoc.fileName);
                return {
                  action: 'delete',
                  path: getDocumentPath(sourceDoc as LcDocument),
                  version: sourceDoc.version || 1,
                  origin: {
                    type: ResourceOrigin.MODULE,
                    projectId: targetProjectId,
                    lastSyncTime: Date.now(),
                  },
                  status: ResourceStatus.REMOTE_DELETED,
                  diff: {
                    before: sourceDoc,
                  },
                };
              } else if (targetDoc.version > (sourceDoc.version || 0)) {
                // 远程有更新
                console.log('检测到远程更新:', sourceDoc.fileName);
                return {
                  action: 'modify',
                  path: getDocumentPath(targetDoc as LcDocument),
                  version: targetDoc.version || 1,
                  origin: {
                    type: ResourceOrigin.MODULE,
                    projectId: targetProjectId,
                    lastSyncTime: Date.now(),
                  },
                  status: ResourceStatus.REMOTE_UPDATED,
                  diff: {
                    before: sourceDoc,
                    after: targetDoc,
                  },
                };
              }
            } else if (targetDoc) {
              // 新增的远程文档
              console.log('检测到新增文档:', targetDoc.fileName);
              return {
                action: 'add',
                path: getDocumentPath(targetDoc as LcDocument),
                version: targetDoc.version || 1,
                origin: {
                  type: ResourceOrigin.MODULE,
                  projectId: targetProjectId,
                  lastSyncTime: Date.now(),
                },
                status: ResourceStatus.NORMAL,
                diff: {
                  after: targetDoc,
                },
              };
            }
            return null;
          },
        );

        // 记录文档变更
        documentChanges.forEach((change) => {
          if (change) {
            const docId = change.diff.after?._id || change.diff.before?._id;
            if (docId) {
              if (
                this.compareLogEnabled({ doc: change.diff.after as LcDocument })
              ) {
                console.log('记录文档变更:', {
                  docId,
                  action: change.action,
                  status: change.status,
                });
              }
              changes.documents[docId] = change as ResourceChange<LcDocument>;
            }
          }
        });
      }

      // 2. 同样处理类型资源
      if (options.fileTypes?.includes('type')) {
        console.log('单向拉取模式 - 开始处理类型，交换源和目标顺序');
        const typeChanges = await this.compareResourcesMetadata(
          targetResources.types, // 本地项目作为 source
          sourceResources.types, // 远程项目作为 target
          (typeEntity) => {
            // 根据 type.controller.ts 中的逻辑生成唯一键
            const type = typeEntity as unknown as ITypeEntity;
            if (type.type === 'api') {
              return `${type.uri || ''}_${type.method || ''}_${type.type}`;
            } else if (['vo', 'cloud_function', 'action'].includes(type.type)) {
              return `${type.name || ''}_${type.type}`;
            } else if (type.type === 'folder') {
              return `${type.folderPath || ''}_${type.type}_${type.name || ''}`;
            } else {
              return `${type.folderPath || type.folder || ''}_${type.type}_${
                type.name || ''
              }`;
            }
          },
          async (sourceType, targetType) => {
            if (
              this.compareLogEnabled({ typeEntity: sourceType as ITypeEntity })
            ) {
              console.log('比较回调 - 类型信息:', {
                sourceType: sourceType
                  ? {
                      id: sourceType._id,
                      name: sourceType.name,
                      from: sourceType.from,
                      version: sourceType.version,
                    }
                  : null,
                targetType: targetType
                  ? {
                      id: targetType._id,
                      name: targetType.name,
                      version: targetType.version,
                    }
                  : null,
                targetProjectId,
              });
            }

            if (
              this.compareLogEnabled({ typeEntity: targetType as ITypeEntity })
            ) {
              console.log('targetType 1', targetType);
              console.log('sourceType 1', sourceType);
            }
            if (
              this.compareLogEnabled({ typeEntity: sourceType as ITypeEntity })
            ) {
              console.log('targetType 2', targetType);
              console.log('sourceType 2', sourceType);
            }

            // 如果本地类型存在且来源是MODULE
            if (sourceType) {
              // 移除 && sourceType.from?.projectId === targetProjectId 进行全面对比，而不是只跟上一次同步来源的文件对比
              if (!targetType) {
                // 远程已删除
                console.log('检测到远程删除:', sourceType.name);
                return {
                  action: 'delete',
                  path: getTypePath(sourceType as ITypeEntity),
                  version: sourceType.version || 1,
                  origin: {
                    type: ResourceOrigin.MODULE,
                    projectId: targetProjectId,
                    lastSyncTime: Date.now(),
                  },
                  status: ResourceStatus.REMOTE_DELETED,
                  diff: {
                    before: sourceType,
                  },
                };
              }
              if (targetType && sourceType) {
                if (targetType.update_time == (sourceType.update_time || 0)) {
                  return null; // 如果更新时间相同，则不进行更新
                }
                // 远程有更新
                // console.log('检测到远程更新:', sourceType.name, getTypePath(targetType as ITypeEntity));
                return {
                  action: 'modify',
                  path: getTypePath(targetType as ITypeEntity),
                  version: targetType.version || 1,
                  origin: {
                    type: ResourceOrigin.MODULE,
                    projectId: targetProjectId,
                    lastSyncTime: Date.now(),
                  },
                  status: ResourceStatus.REMOTE_UPDATED,
                  diff: {
                    before: sourceType,
                    after: targetType,
                  },
                };
              }
            }
            if (targetType) {
              // 新增的远程类型
              if (
                this.compareLogEnabled({
                  typeEntity: targetType as ITypeEntity,
                })
              ) {
                console.log('检测到新增类型:', targetType);
              }
              return {
                action: 'add',
                path: getTypePath(targetType as ITypeEntity),
                version: targetType.version || 1,
                origin: {
                  type: ResourceOrigin.MODULE,
                  projectId: targetProjectId,
                  lastSyncTime: Date.now(),
                },
                status: ResourceStatus.NORMAL,
                diff: {
                  after: targetType,
                },
              };
            }
            return null;
          },
        );

        // 记录类型变更
        typeChanges.forEach((change) => {
          if (change) {
            const typeId = change.diff.after?._id || change.diff.before?._id;
            if (typeId) {
              // if (
              //   this.compareLogEnabled({
              //     typeEntity: change.diff.after as ITypeEntity,
              //   })
              // ) {
              //   console.log('记录类型变更:', {
              //     typeId,
              //     action: change.action,
              //     status: change.status,
              //   });
              // }
              changes.types[typeId] = change as ResourceChange<ITypeEntity>;
            }
          }
        });
      }

      return {
        sourceProject: {
          project: this.pickProjectBasicInfo(sourceResources.project),
          folder: sourceFolder,
        },
        targetProject: {
          project: this.pickProjectBasicInfo(targetResources.project),
          folder: targetFolder,
        },
        changes,
        summary: this.calculateChangeSummary(changes),
      };
    } else {
      // 根据比较模式处理文档和类型比较
      if (options.fileTypes?.includes('document')) {
        const documentChanges = await this.compareResourcesMetadata(
          sourceResources.documents,
          targetResources.documents,
          (doc) => `${doc.fileName || ''}_${doc.folder || ''}`,
          async (sourceDoc, targetDoc) => {
            if (sourceDoc.update_time !== targetDoc.update_time) {
              const [fullSourceDoc, fullTargetDoc] = await Promise.all([
                this.documentModel.findById(sourceDoc._id).lean(),
                this.documentModel.findById(targetDoc._id).lean(),
              ]);

              return {
                action: 'modify',
                path: getDocumentPath(sourceDoc as LcDocument),
                version: targetDoc.version || 1,
                origin: {
                  type: ResourceOrigin.REMOTE,
                  projectId: targetProjectId,
                  lastSyncTime: targetDoc.update_time,
                },
                status: ResourceStatus.REMOTE_UPDATED,
                diff: {
                  before: fullSourceDoc,
                  after: fullTargetDoc,
                },
              };
            }
            return null;
          },
        );

        // 处理文档变更
        documentChanges.forEach((change) => {
          if (change) {
            const docId = change.diff.before?._id || change.diff.after?._id;
            changes.documents[docId] = change as ResourceChange<LcDocument>;
          }
        });
      }

      // 比较类型
      if (options.fileTypes?.includes('type')) {
        // console.log('开始比对类型资源...');
        const typeChanges = await this.compareResourcesMetadata(
          sourceResources.types,
          targetResources.types,
          (typeEntity) => {
            // 根据 type.controller.ts 中的逻辑生成唯一键
            const type = typeEntity as unknown as ITypeEntity;
            if (type.type === 'api') {
              return `${type.uri || ''}_${type.method || ''}_${type.type}_${
                type.projectId || ''
              }`;
            } else if (['vo', 'cloud_function', 'action'].includes(type.type)) {
              return `${type.name || ''}_${type.type}_${type.projectId || ''}`;
            } else if (type.type === 'folder') {
              return `${type.folderPath || ''}_${type.type}_${
                type.projectId || ''
              }`;
            } else {
              return `${type.unikey || ''}_${type.projectId || ''}`;
            }
          },
          async (sourceType, targetType) => {
            if (
              this.compareLogEnabled({ typeEntity: sourceType as ITypeEntity })
            ) {
              console.log('比对类型: ', {
                sourceType: sourceType
                  ? {
                      id: sourceType._id,
                      name: sourceType.name,
                      updateTime: sourceType.update_time,
                    }
                  : null,
                targetType: targetType
                  ? {
                      id: targetType._id,
                      name: targetType.name,
                      updateTime: targetType.update_time,
                    }
                  : null,
              });
            }

            if (!sourceType && targetType) {
              console.log('发现新增类型:', targetType.name);
              return {
                action: 'add',
                path: getTypePath(targetType as ITypeEntity),
                version: targetType.version || 1,
                origin: {
                  type: ResourceOrigin.REMOTE,
                  projectId: targetProjectId,
                  lastSyncTime: targetType.update_time,
                },
                status: ResourceStatus.NORMAL,
                diff: {
                  after: targetType,
                },
              };
            } else if (
              sourceType &&
              targetType &&
              sourceType.update_time !== targetType.update_time
            ) {
              console.log('发现需要更新的类型:', targetType.name);
              const [fullSourceType, fullTargetType] = await Promise.all([
                this.typeModel.findById(sourceType._id).lean(),
                this.typeModel.findById(targetType._id).lean(),
              ]);

              console.log('类型详细信息: ', {
                sourceName: fullSourceType.name,
                sourceUpdateTime: fullSourceType.update_time,
                targetName: fullTargetType.name,
                targetUpdateTime: fullTargetType.update_time,
              });

              return {
                action: 'modify',
                path: getTypePath(sourceType as ITypeEntity),
                version: targetType.version || 1,
                origin: {
                  type: ResourceOrigin.REMOTE,
                  projectId: targetProjectId,
                  lastSyncTime: targetType.update_time,
                },
                status: ResourceStatus.REMOTE_UPDATED,
                diff: {
                  before: fullSourceType,
                  after: fullTargetType,
                },
              };
            }
            return null;
          },
        );

        console.log('类型比对结果: ', {
          totalChanges: typeChanges.length,
          changes: typeChanges.map((change) => ({
            action: change.action,
            typeName: change.diff.after?.name || change.diff.before?.name,
            updateTime:
              change.diff.after?.update_time || change.diff.before?.update_time,
          })),
        });

        // 处理类型变更
        typeChanges.forEach((change) => {
          if (change) {
            const typeId = change.diff.before?._id || change.diff.after?._id;
            if (
              this.compareLogEnabled({
                typeEntity: change.diff.after as ITypeEntity,
              })
            ) {
              console.log('记录类型变更2:', {
                typeId,
                action: change.action,
                status: change.status,
              });
            }
            changes.types[typeId] = change as ResourceChange<ITypeEntity>;
          }
        });
      }
    }

    // 计算变更统计
    const summary = this.calculateChangeSummary(changes);

    // 返回比较结果
    return {
      sourceProject: {
        project: this.pickProjectBasicInfo(sourceResources.project),
        folder: sourceFolder,
      },
      targetProject: {
        project: this.pickProjectBasicInfo(targetResources.project),
        folder: targetFolder,
      },
      changes,
      summary,
      scope: {
        basic: false,
        config: !!options.compareConfig,
        assets: !!options.compareAssets,
        documents: options.fileTypes?.includes('document') || false,
        types: options.fileTypes?.includes('type') || false,
      },
    };
  }

  /**
   * 比较项目基本信息
   */
  private async compareProjectBasicInfo(
    sourceProject: LcProject,
    targetProject: LcProject,
  ): Promise<
    | ResourceChange<
        Pick<
          LcProject,
          'title' | 'description' | 'type' | 'icon' | 'screenshot'
        > & { version?: number }
      >
    | undefined
  > {
    const basicFields: Array<
      keyof Pick<
        LcProject,
        'title' | 'description' | 'type' | 'icon' | 'screenshot'
      >
    > = ['title', 'description', 'type', 'icon', 'screenshot'];

    const before = pick(sourceProject, basicFields);
    const after = pick(targetProject, basicFields);

    if (!isEqual(before, after)) {
      return {
        action: 'modify',
        path: '/',
        version: targetProject.version || 1,
        origin: {
          type: ResourceOrigin.REMOTE,
          projectId: targetProject._id,
          lastSyncTime: targetProject.update_time,
        },
        status: ResourceStatus.REMOTE_UPDATED,
        diff: { before, after },
      };
    }

    return undefined;
  }

  /**
   * 比较项目配置
   */
  private async compareProjectConfig(
    sourceProject: LcProject,
    targetProject: LcProject,
  ): Promise<
    | ResourceChange<
        Pick<LcProject, 'envInfo' | 'appConfig'> & { version?: number }
      >
    | undefined
  > {
    const configFields: Array<keyof Pick<LcProject, 'envInfo' | 'appConfig'>> =
      ['envInfo', 'appConfig'];

    const before = pick(sourceProject, configFields);
    const after = pick(targetProject, configFields);

    if (!isEqual(before, after)) {
      return {
        action: 'modify',
        path: '/',
        version: targetProject.version || 1,
        origin: {
          type: ResourceOrigin.REMOTE,
          projectId: targetProject._id,
          lastSyncTime: targetProject.update_time,
        },
        status: ResourceStatus.REMOTE_UPDATED,
        diff: { before, after },
      };
    }

    return undefined;
  }

  /**
   * 比较项目资源
   */
  private async compareProjectAssets(
    sourceProject: LcProject,
    targetProject: LcProject,
  ): Promise<
    | ResourceChange<
        Pick<LcProject, 'assets' | 'packages' | 'themes'> & { version?: number }
      >
    | undefined
  > {
    const assetFields: Array<
      keyof Pick<LcProject, 'assets' | 'packages' | 'themes'>
    > = ['assets', 'packages', 'themes'];

    const before = pick(sourceProject, assetFields);
    const after = pick(targetProject, assetFields);

    if (!isEqual(before, after)) {
      return {
        action: 'modify',
        path: '/',
        version: targetProject.version || 1,
        origin: {
          type: ResourceOrigin.REMOTE,
          projectId: targetProject._id,
          lastSyncTime: targetProject.update_time,
        },
        status: ResourceStatus.REMOTE_UPDATED,
        diff: { before, after },
      };
    }

    return undefined;
  }

  /**
   * 比较指定文件
   */
  private async compareSpecifiedFiles(
    sourceResources: Awaited<ReturnType<typeof this.getAllProjectResources>>,
    targetResources: Awaited<ReturnType<typeof this.getAllProjectResources>>,
    fileIds: string[],
    changes: ApiEditorCompareProjectFoldersResDto['changes'],
  ) {
    for (const fileId of fileIds) {
      const sourceDoc = sourceResources.documents.find(
        (d) => d._id.toString() === fileId,
      );
      const targetDoc = targetResources.documents.find(
        (d) => d._id.toString() === fileId,
      );

      if (sourceDoc && targetDoc) {
        // 文档比较
        const change = await this.compareResourcesMetadata(
          [sourceDoc],
          [targetDoc],
          (doc) => `${doc.fileName || ''}_${doc.folder || ''}`,
          async (source, target) => {
            if (source.update_time !== target.update_time) {
              const [fullSource, fullTarget] = await Promise.all([
                this.documentModel.findById(source._id).lean(),
                this.documentModel.findById(target._id).lean(),
              ]);

              return {
                action: 'modify',
                path: source.folder || '',
                version: target.version || 1,
                origin: {
                  type: ResourceOrigin.REMOTE,
                  projectId: targetResources.project._id,
                  lastSyncTime: target.update_time,
                },
                status: ResourceStatus.REMOTE_UPDATED,
                diff: {
                  before: fullSource,
                  after: fullTarget,
                },
              };
            }
            return null;
          },
        );

        if (change?.[0]) {
          changes.documents[fileId] = change[0] as ResourceChange<LcDocument>;
        }
      }

      const sourceType = sourceResources.types.find(
        (t) => t._id.toString() === fileId,
      );
      const targetType = targetResources.types.find(
        (t) => t._id.toString() === fileId,
      );

      if (sourceType && targetType) {
        // 类型比较
        const change = await this.compareResourcesMetadata(
          [sourceType],
          [targetType],
          (typeEntity) => {
            // 根据 type.controller.ts 中的逻辑生成唯一键
            const type = typeEntity as unknown as ITypeEntity;
            if (type.type === 'api') {
              return `${type.uri || ''}_${type.method || ''}_${type.type}_${
                type.projectId || ''
              }`;
            } else if (['vo', 'cloud_function', 'action'].includes(type.type)) {
              return `${type.name || ''}_${type.type}_${type.projectId || ''}`;
            } else if (type.type === 'folder') {
              return `${type.folderPath || ''}_${type.type}_${
                type.projectId || ''
              }`;
            } else {
              return `${type.unikey || ''}_${type.projectId || ''}`;
            }
          },
          async (source, target) => {
            if (source.update_time !== target.update_time) {
              const [fullSource, fullTarget] = await Promise.all([
                this.typeModel.findById(source._id).lean(),
                this.typeModel.findById(target._id).lean(),
              ]);

              return {
                action: 'modify',
                path: source.folder || '',
                version: target.version || 1,
                origin: {
                  type: ResourceOrigin.REMOTE,
                  projectId: targetResources.project._id,
                  lastSyncTime: target.update_time,
                },
                status: ResourceStatus.REMOTE_UPDATED,
                diff: {
                  before: fullSource,
                  after: fullTarget,
                },
              };
            }
            return null;
          },
        );

        if (change?.[0]) {
          changes.types[fileId] = change[0] as ResourceChange<ITypeEntity>;
        }
      }
    }
  }

  /**
   * 计算变更统计信息
   */
  private calculateChangeSummary(
    changes: ApiEditorCompareProjectFoldersResDto['changes'],
  ): ApiEditorCompareProjectFoldersResDto['summary'] {
    const summary: ApiEditorCompareProjectFoldersResDto['summary'] = {
      project: changes.project
        ? {
            basic: !!changes.project.basic,
            config: !!changes.project.config,
            assets: !!changes.project.assets,
          }
        : undefined,
      documents: {
        added: 0,
        modified: 0,
        deleted: 0,
        remoteDeleted: 0,
        remoteUpdated: 0,
        localModified: 0,
        conflicts: 0,
      },
      types: {
        added: 0,
        modified: 0,
        deleted: 0,
        remoteDeleted: 0,
        remoteUpdated: 0,
        localModified: 0,
        conflicts: 0,
      },
    };

    // 统计文档变更
    Object.values(changes.documents).forEach((change) => {
      if (change.action === 'add') summary.documents.added++;
      if (change.action === 'modify') summary.documents.modified++;
      if (change.status === ResourceStatus.REMOTE_DELETED)
        summary.documents.remoteDeleted++;
      if (change.status === ResourceStatus.REMOTE_UPDATED)
        summary.documents.remoteUpdated++;
      if (change.status === ResourceStatus.LOCAL_MODIFIED)
        summary.documents.localModified++;
      if (change.status === ResourceStatus.CONFLICT)
        summary.documents.conflicts++;
    });

    // 统计类型变更
    Object.values(changes.types).forEach((change) => {
      if (change.action === 'add') summary.types.added++;
      if (change.action === 'modify') summary.types.modified++;
      if (change.status === ResourceStatus.REMOTE_DELETED)
        summary.types.remoteDeleted++;
      if (change.status === ResourceStatus.REMOTE_UPDATED)
        summary.types.remoteUpdated++;
      if (change.status === ResourceStatus.LOCAL_MODIFIED)
        summary.types.localModified++;
      if (change.status === ResourceStatus.CONFLICT) summary.types.conflicts++;
    });

    return summary;
  }

  /**
   * 提取项目基本信息
   */
  private pickProjectBasicInfo(
    project: LcProject,
  ): Pick<LcProject, '_id' | 'title' | 'description' | 'type' | 'teamId'> {
    return pick(project, ['_id', 'title', 'description', 'type', 'teamId']);
  }

  /**
   * 基于元数据比较资源
   * @param sourceList 源资源列表
   * @param targetList 目标资源列表
   * @param getUniqueKey 生成资源唯一标识的函数
   * @param compareCallback 当发现差异时的回调函数
   */
  private async compareResourcesMetadata<
    T extends {
      _id: any;
      update_time?: number;
      folder?: string;
      version?: number;
    },
  >(
    sourceList: T[],
    targetList: T[],
    getUniqueKey: (item: T) => string,
    compareCallback: (
      source: T | null,
      target: T | null,
    ) => Promise<ResourceChange<T> | null>,
  ): Promise<Array<ResourceChange<T> | null>> {
    const changes: Array<ResourceChange<T> | null> = [];
    if (
      this.compareLogEnabled({
        typeEntity: sourceList[0] as unknown as ITypeEntity,
        doc: sourceList[0] as unknown as LcDocument,
      })
    ) {
      console.log('compareResourcesMetadata 开始比对: ', {
        sourceListLength: sourceList.length,
        targetListLength: targetList.length,
        sourceListSample: sourceList.slice(0, 3).map((item) => ({
          id: item._id?.toString(),
          key: getUniqueKey(item),
          updateTime: item.update_time,
        })),
        targetListSample: targetList.slice(0, 3).map((item) => ({
          id: item._id?.toString(),
          key: getUniqueKey(item),
          updateTime: item.update_time,
        })),
      });
    }
    // 创建查找映射
    const sourceMap = new Map(
      sourceList.map((item) => [getUniqueKey(item), item]),
    );
    const targetMap = new Map(
      targetList.map((item) => [getUniqueKey(item), item]),
    );

    console.log('映射关系: ', {
      sourceMapSize: sourceMap.size,
      targetMapSize: targetMap.size,
      sourceMapKeys: Array.from(sourceMap.keys()),
      targetMapKeys: Array.from(targetMap.keys()),
    });

    // 处理所有目标资源
    for (const targetItem of targetList) {
      const uniqueKey = getUniqueKey(targetItem);
      const sourceItem = sourceMap.get(uniqueKey);
      const targetId = targetItem._id?.toString();

      if (
        this.compareLogEnabled({
          typeEntity: targetItem as unknown as ITypeEntity,
          doc: targetItem as unknown as LcDocument,
        })
      ) {
        console.log('处理目标资源: ', {
          targetItemId: targetId,
          uniqueKey,
          hasSourceItem: !!sourceItem,
          sourceItemId: sourceItem?._id?.toString(),
          sourceItemUpdateTime: sourceItem?.update_time,
          targetItemUpdateTime: targetItem.update_time,
        });
      }

      if (!sourceItem) {
        // 远程有而本地没有的资源，标记为新增
        if (targetId) {
          if (
            this.compareLogEnabled({
              typeEntity: targetItem as unknown as ITypeEntity,
              doc: targetItem as unknown as LcDocument,
            })
          ) {
            console.log('添加新增记录: ', targetId);
          }
          const change = await compareCallback(null, targetItem);
          if (change) {
            changes.push(change);
          }
        }
      } else if (sourceItem.update_time !== targetItem.update_time) {
        // 对于已存在且时间戳不同的资源，调用比较回调
        if (
          this.compareLogEnabled({
            typeEntity: sourceItem as unknown as ITypeEntity,
            doc: sourceItem as unknown as LcDocument,
          })
        ) {
          console.log('调用比较回调: ', {
            sourceItemId: sourceItem._id?.toString(),
            targetItemId: targetId,
            sourceUpdateTime: sourceItem.update_time,
            targetUpdateTime: targetItem.update_time,
          });
        }
        const change = await compareCallback(sourceItem, targetItem);
        if (change) {
          if (
            this.compareLogEnabled({
              typeEntity: sourceItem as unknown as ITypeEntity,
              doc: sourceItem as unknown as LcDocument,
            })
          ) {
            console.log(
              `添加${
                change.action === 'add'
                  ? '新增'
                  : change.action === 'modify'
                  ? '修改'
                  : '删除'
              }记录: `,
              change,
            );
          }
          changes.push(change);
        }
      }
    }

    // 处理删除的资源
    for (const sourceItem of sourceList) {
      const uniqueKey = getUniqueKey(sourceItem);
      if (!targetMap.has(uniqueKey)) {
        // 对于本地有而远程没有的资源，调用比较回调
        if (
          this.compareLogEnabled({
            typeEntity: sourceItem as unknown as ITypeEntity,
            doc: sourceItem as unknown as LcDocument,
          })
        ) {
          console.log('处理可能的删除: ', {
            sourceItemId: sourceItem._id?.toString(),
            uniqueKey,
          });
        }
        const change = await compareCallback(sourceItem, null);
        if (change) {
          if (
            this.compareLogEnabled({
              typeEntity: sourceItem as unknown as ITypeEntity,
              doc: sourceItem as unknown as LcDocument,
            })
          ) {
            console.log('添加删除记录: ', change);
          }
          changes.push(change);
        }
      }
    }

    console.log('比对结果: ', {
      totalChanges: changes.length,
      changesAfterFilter: changes.filter(Boolean).length,
      changeTypes: changes.filter(Boolean).map((c) => c.action),
    });

    // 过滤掉空值
    return changes.filter(Boolean);
  }

  /**
   * 生成项目文件快照数组
   * 加载全部资源并转换为 FileSnapshot 格式
   */
  async generateFileSnapshots(params: {
    projectId: string;
    folder?: string;
    branch?: string;
    userId?: number;
  }): Promise<FileSnapshot[]> {
    const { projectId, folder, branch, userId } = params;

    // 获取项目信息
    const project = await this.projectModel.findById(projectId).select({
      _id: 1,
      title: 1,
      hash: 1,
      update_time: 1,
      lastUpdateBy: 1,
      folder: 1,
    });

    if (!project) {
      throw new BadRequestException('项目不存在');
    }

    // 权限检查
    if (userId) {
      const havePermission = await this.checkAuth(project, 'view', userId);
      if (!havePermission) {
        throw new BadRequestException('您无权访问该项目');
      }
    }

    // 构建查询条件
    const baseQuery: any = {
      projectId: new mongoose.Types.ObjectId(projectId),
    };

    // 文件夹过滤
    if (folder) {
      baseQuery.folder = { $regex: `^${folder === '/' ? '' : folder}/?.*` };
    }

    // 分支过滤 - 版本控制场景下不过滤分支
    if (branch) {
      baseQuery.branch = branch;
    }

    // 🔧 文档查询条件：过滤 .doc 后缀和 /globalStorage 开头
    const documentQuery = {
      ...baseQuery,
      // $and: [
      //   { fileName: { $not: /\.doc$/ } }, // 排除 .doc 后缀
      //   { fileName: { $not: /^\/globalStorage/ } }, // 排除 /globalStorage 开头
      // ],
    };

    // 🔧 类型查询条件：过滤 folder 类型和 /globalStorage 开头
    const typeQuery = {
      ...baseQuery,
      type: { $ne: 'folder' }, // 排除 folder 类型
      $nor: [
        { name: /^\/globalStorage/ }, // 排除 name 以 /globalStorage 开头
        { fileName: /^\/globalStorage/ }, // 排除 fileName 以 /globalStorage 开头
      ],
    };

    // 并行获取文档和类型
    const [documents, types] = await Promise.all([
      this.documentModel
        .find(documentQuery)
        .select({
          _id: 1,
          fileName: 1,
          title: 1,
          type: 1,
          folder: 1,
          folderPath: 1,
          hash: 1,
          update_time: 1,
          lastUpdateBy: 1,
          branch: 1,
          from: 1,
        })
        .lean(),
      this.typeModel
        .find(typeQuery)
        .select({
          _id: 1,
          name: 1,
          type: 1,
          folder: 1,
          folderPath: 1,
          hash: 1,
          update_time: 1,
          lastUpdateBy: 1,
          fileName: 1,
          // 🔧 添加缺失的字段
          uri: 1, // API 路径
          method: 1, // API 方法
          language: 1, // 代码语言
        })
        .lean(),
    ]);

    // 调试日志（可选）
    console.log(`🔍 [generateFileSnapshots] 项目 ${projectId}:`);
    console.log(`  📄 查询到文档数: ${documents.length}`);
    console.log(`  🔧 查询到类型数: ${types.length}`);
    console.log(`  📋 文档查询条件:`, JSON.stringify(documentQuery, null, 2));
    console.log(`  📋 类型查询条件:`, JSON.stringify(typeQuery, null, 2));
    console.log(
      `  📄 文档路径:`,
      documents.map((d) => d.fileName || d.title).slice(0, 10),
    );
    console.log(
      `  🔧 类型路径:`,
      types.map((t) => t.name || t.fileName).slice(0, 10),
    );

    const snapshots: FileSnapshot[] = [];

    // 添加项目快照 - 使用 hash 或 update_time 作为 hash
    const projectHash =
      project.hash || String(project.update_time || Date.now());
    snapshots.push({
      path: `project:${project._id}`,
      modify: project.update_time || Date.now(),
      hash: projectHash,
      updateBy: project.lastUpdateBy || 'system',
    });

    // 添加文档快照 - 使用 hash 或 update_time 作为 hash
    let docsWithHash = 0;
    let docsWithFallbackHash = 0;
    for (const doc of documents) {
      const path = getDocumentPath(doc as any);

      // 🔧 二次过滤：排除 .doc 后缀和 globalStorage 开头的路径
      // 注意：路径可能是 /globalStorage 或 /\globalStorage
      if (
        path.startsWith('/globalStorage') ||
        path.startsWith('/\\globalStorage') ||
        path.includes('globalStorage')
      ) {
        continue;
      }

      const docHash = doc.hash || String(doc.update_time || Date.now());

      snapshots.push({
        path: path.replace('.doc', '.tsx'),
        modify: doc.update_time || Date.now(),
        hash: docHash,
        updateBy: doc.lastUpdateBy || 'system',
      });

      if (doc.hash) {
        docsWithHash++;
      } else {
        docsWithFallbackHash++;
        if (process.env.NODE_ENV === 'development') {
          console.log(
            `  🔄 文档使用fallback hash: ${doc.fileName || doc.title} (${
              doc.type
            }) -> ${docHash}`,
          );
        }
      }
    }
    if (process.env.NODE_ENV === 'development') {
      console.log(
        `  📄 有hash的文档: ${docsWithHash}, 使用fallback hash的文档: ${docsWithFallbackHash}`,
      );
    }

    // 添加类型快照 - 使用 hash 或 update_time 作为 hash
    let typesWithHash = 0;
    let typesWithFallbackHash = 0;
    for (const type of types) {
      const path = getTypePath(type as any);

      // 🔧 二次过滤：排除 /globalStorage 开头的路径
      if (path.startsWith('/globalStorage')) {
        continue;
      }

      const typeHash = type.hash || String(type.update_time || Date.now());

      snapshots.push({
        path,
        modify: type.update_time || Date.now(),
        hash: typeHash,
        updateBy: (type as any).lastUpdateBy || 'system',
      });

      if (type.hash) {
        typesWithHash++;
      } else {
        typesWithFallbackHash++;
        if (process.env.NODE_ENV === 'development') {
          console.log(
            `  🔄 类型使用fallback hash: ${type.name} (${type.type}) -> ${typeHash}`,
          );
        }
      }
    }
    if (process.env.NODE_ENV === 'development') {
      console.log(
        `  🔧 有hash的类型: ${typesWithHash}, 使用fallback hash的类型: ${typesWithFallbackHash}`,
      );
      console.log(`  📊 最终快照数量: ${snapshots.length}`);
    }

    return snapshots;
  }

  /**
   * 通用快照比对算法
   * 比对两个 FileSnapshot 数组，返回 FileChangeWithSide 数组
   */
  compareFileSnapshots<T = any>(params: {
    sourceSnapshots: FileSnapshot[];
    targetSnapshots: FileSnapshot[];
    sourceLabel?: string;
    targetLabel?: string;
    mode?: 'bidirectional' | 'unidirectional'; // bidirectional: 双向比对, unidirectional: 单向比对(只关注target的变更)
  }): FileChangeWithSide<T>[] {
    const {
      sourceSnapshots,
      targetSnapshots,
      sourceLabel = 'source',
      targetLabel = 'target',
      mode = 'bidirectional',
    } = params;

    const changes: FileChangeWithSide<T>[] = [];
    const now = Date.now();

    // 创建路径映射
    const sourceMap = new Map<string, FileSnapshot>();
    const targetMap = new Map<string, FileSnapshot>();

    sourceSnapshots.forEach((snapshot) => {
      sourceMap.set(snapshot.path, snapshot);
    });

    targetSnapshots.forEach((snapshot) => {
      targetMap.set(snapshot.path, snapshot);
    });

    // 处理目标快照中的文件（新增和修改）
    for (const targetSnapshot of targetSnapshots) {
      const sourceSnapshot = sourceMap.get(targetSnapshot.path);

      if (!sourceSnapshot) {
        // 新增文件
        changes.push({
          id: `add_${targetSnapshot.path}_${now}`,
          action: 'add',
          path: targetSnapshot.path,
          version: 1,
          timestamp: targetSnapshot.modify,
          diff: {
            after: { hash: targetSnapshot.hash } as unknown as Partial<T>,
          },
          side: 'target',
          sourceLabel,
          targetLabel,
          target: targetSnapshot, // 🔧 添加目标快照
        } as any);
      } else if (sourceSnapshot.hash !== targetSnapshot.hash) {
        // 修改文件
        changes.push({
          id: `modify_${targetSnapshot.path}_${now}`,
          action: 'modify',
          path: targetSnapshot.path,
          version: 1,
          timestamp: Math.max(sourceSnapshot.modify, targetSnapshot.modify),
          diff: {
            before: { hash: sourceSnapshot.hash } as unknown as Partial<T>,
            after: { hash: targetSnapshot.hash } as unknown as Partial<T>,
          },
          side:
            sourceSnapshot.modify < targetSnapshot.modify ? 'target' : 'source',
          sourceLabel,
          targetLabel,
          source: sourceSnapshot, // 🔧 添加源快照
          target: targetSnapshot, // 🔧 添加目标快照
        } as any);
      }
    }

    // 处理删除的文件（仅在双向比对模式下）
    if (mode === 'bidirectional') {
      for (const sourceSnapshot of sourceSnapshots) {
        if (!targetMap.has(sourceSnapshot.path)) {
          changes.push({
            id: `delete_${sourceSnapshot.path}_${now}`,
            action: 'delete',
            path: sourceSnapshot.path,
            version: 1,
            timestamp: sourceSnapshot.modify,
            diff: {
              before: { hash: sourceSnapshot.hash } as unknown as Partial<T>,
            },
            side: 'source',
            sourceLabel,
            targetLabel,
            source: sourceSnapshot, // 🔧 添加源快照
          } as any);
        }
      }
    }

    return changes;
  }

  /**
   * 获取项目下所有资源
   */
  async getAllProjectResources(params: {
    projectId: string;
    folder?: string;
    userId?: number; // 添加用户ID用于权限检查
  }) {
    const { projectId, folder, userId } = params;

    // 获取项目信息
    const project = await this.projectModel.findById(projectId).select({
      schema: 0,
      packages: 0,
      assets: 0,
    });
    if (!project) {
      throw new BadRequestException('项目不存在');
    }

    // 如果提供了用户ID,进行权限检查
    if (userId) {
      const havePermission = await this.checkAuth(project, 'view', userId);
      if (!havePermission) {
        throw new BadRequestException('您无权访问该项目');
      }
    }

    // 构建查询条件
    const baseQuery: any = {
      projectId: new mongoose.Types.ObjectId(projectId),
    };
    if (folder) {
      // 构建文件夹匹配条件
      const folderRegex = new RegExp(`^${folder === '/' ? '' : folder}/?.*`);
      baseQuery.folder = { $regex: `^${folder === '/' ? '' : folder}/?.*` };
      console.log('文件夹匹配条件: ', {
        folder,
        regex: folderRegex.toString(),
      });
    }

    console.log('查询资源条件: ', baseQuery);

    // 并行获取文档和类型
    const [documents, types] = await Promise.all([
      this.documentModel
        .find(baseQuery)
        .select({
          schema: 0,
          types: 0,
          componentsMap: 0,
          utilsMap: 0,
        })
        .lean(),
      this.typeModel
        .find(baseQuery)
        .select({
          source: 0,
          fields: 0,
          code: 0,
        })
        .lean(),
    ]);

    console.log('查询到的资源: ', {
      projectId,
      folder,
      documentsCount: documents.length,
      typesCount: types.length,
    });

    // 打印所有类型的文件夹路径
    console.log(
      '类型文件夹分布: ',
      types.reduce((acc, type) => {
        const folder = type.folder || '未分类';
        acc[folder] = (acc[folder] || 0) + 1;
        return acc;
      }, {} as Record<string, number>),
    );

    return {
      project,
      documents,
      types,
      folder,
    };
  }
}
